
# calendar
from datetime import date, datetime
from time import strptime, mktime
from itertools import groupby

from django.utils.html import conditional_escape as esc
from django.utils.encoding import smart_str, smart_unicode

from mysite.tj3.models import *
from mysite.settings import *
import os.path
import subprocess
import csv
from django.core import serializers
from lxml import etree
from string import capwords

# to handle emails
import poplib
import getpass

def BuildWBS (tasks):
	wbs={}
	registered={t.id:0 for t in tasks}
	for	l in range(10):
		tt=tasks.filter(level=l)
		for i in range(len(tt)):
			t=tt[i]
			if t.parent:
				wbs[t.id]=wbs[t.parent.id].zfill(2)+'.'+str(registered[t.parent.id]+1).zfill(2)
				#wbs[t.id]=wbs[t.parent.id]+'.'+str(registered[t.parent.id]+1)
				registered[t.parent.id]+=1
	
			else: 
				wbs[t.id]=str(i+1).zfill(2)
				#wbs[t.id]=str(i+1)
	return wbs
                	                                                                            				
def UpdateBudget(task, scenario):
	# We are to walk through the entire tree from this "leaf" update to update all ancestors' budgets.
	# Starting from the bottom and rollup all the way to the top.
	# This means that container tasks would always have a budget = sum(children)
	# even though you want to manually set a value that is different!
	# No exception!
	tb, created=TaskBudget.objects.get_or_create(task=task,scenario=scenario)
	
	if task.parent:
		budgets=[]
		reserves=[]
		for t in task.get_siblings(include_self=True):
			tb, created=TaskBudget.objects.get_or_create(task=t,scenario=scenario)
			if not tb.budget: budgets.append(0)
			else: budgets.append(tb.budget)
			
			if not tb.reserve: reserves.append(0)
			else: reserves.append(tb.reserve)
		p, created=TaskBudget.objects.get_or_create(task=task.parent,scenario=scenario)
		p.budget=sum(budgets)
		p.reserve=sum(reserves)
		p.save()
		
		# bubble up
		UpdateBudget(task.parent,scenario)
	
def ProjectDumpToFile(proj, scenario, dest='/tmp', file='auto_plan.tjp'):
	tasks=Task.tree.filter(project=proj)

	# project info
	p = '''project %s "%s" %s - %s {
		timeformat "%s"
		numberformat %s
		currencyformat %s
		currency "%s"
		scenario %s "%s" {
			scenario %s "%s" {}
		}
		trackingscenario %s
		}		
		''' % (CleanName(proj.name), proj.description, proj.start, proj.end, 
				proj.timeformat, proj.numberformat, proj.currencyformat, proj.currency,
				proj.scenario.name,proj.scenario.description,
				scenario.name, scenario.description, scenario.name)
	p=re.sub('\t+',' ',p)

	# Reformat indents.
	aa =[a.strip() for a in p.strip().split('\n')]
	bb=['\t'+b for b in aa[1:-1]]
	p='\n'.join([aa[0]]+bb+[aa[-1]])+'\n'

	
	# vacations, TBD
			
	# flags
	flags = []
	for t in tasks:
		flags += [a.name for a in t.tags.all()]
	for r in Resource.objects.all():
		flags += [a.name for a in r.tags.all()]
	flags=list(set(flags))
	
	flags =['flags %s' % t for t in flags]
	p+= '\n'.join(flags)+'\n'

	# task.tji, resource.tji, journal.tji
	task_tji='auto_task.tji'
	TaskDumpToFile(tasks,dest, task_tji)
	
	# dump resource
	resource_tji='auto_resource.tji'
	ResourceDumpToFile(tasks, dest, resource_tji)
		
	# dump journal
	journal_tji='auto_journal.tji'
	bookings=Booking.objects.filter(task__in=tasks).order_by('-when')
	journals=dict([(b,Journal.objects.filter(booking=b)) for b in bookings])	
	JournalDumpToFile(proj, journals, dest, journal_tji)
	
	# this is always there, to facilitate custome report generation
	report_tji='auto_report.tji'


	p+='include "%s"\n' % resource_tji
	p+='include "%s"\n' % task_tji
	p+='include "%s"\n' % journal_tji		
	p+='include "%s"\n' % report_tji
	
	open(os.path.join(dest,file),'w').write(p)
	return (task_tji,resource_tji,journal_tji, report_tji)

def ResourceDumpToFile(tasks, dest='/tmp', file='auto_resource.tji'):
	r=[]
	for t in tasks:
		temp=AllocateResource.objects.filter(task=t)
		if temp:
			for a in temp:
				r.append(a.primary.id)
				if a.alternative: r.append(a.alternative.id)
	r=list(set(r))
	
	p = []
	for r in [Resource.objects.get(id=int(b)) for b in r]:
		a='''resource %s "%s %s" {
			email "%s"\n''' % (r,r.first_name,r.last_name,r.email)
		
		if len(r.tags.all()): a+='flags '+','.join([d.name for d in r.tags.all()])+'\n'
		
		# dump timeoff
		for timeoff in TimeOff.objects.filter(resource=r):
			a += 'vacation %s-09:00 - %s-18:00\n' % (timeoff.start, timeoff.end)
		
		# dump worklimit
		wl=r.work_limit
		limits=[]
		if wl.dailymax: limits.append('dailymax '+wl.dailymax)
		if wl.dailymin: limits.append('dailymin '+wl.dailymin)
		if wl.weeklymax: limits.append('weeklymax '+wl.weeklymax)
		if wl.weeklymin: limits.append('weeklymin '+wl.weeklymin)
		if wl.monthlymax: limits.append('monthlymax '+wl.monthlymax)
		if wl.monthlymin: limits.append('monthlymin '+wl.monthlymin)
		if wl.maximum: limits.append('maximum '+wl.maximum)
		if wl.minimum: limits.append('minimum '+wl.minimum)
		a+='limits {%s}\n' % ' '.join(limits)
		
		a+='}\n'
		
		# Reformat the string. Too many indents.
		aa=[temp.strip() for temp in a.strip().split('\n')]
		bb=['\t'+b for b in aa[1:-1]]
		cc='\n'.join([aa[0]]+bb+[aa[-1]])
		p.append(cc)
		
	open(os.path.join(dest,file),'w').write('\n'.join(p))	

def TaskDumpToFile(tasks, dest='/tmp', file='auto_task.tji'):
	# populate full-path of a task
	for t in tasks:
		# populate full path
		paths =[CleanName(a.name) for a in t.get_ancestors()]
		paths.append(CleanName(t.name))
		t.full_path = '.'.join(paths)
		t.save()

	tji_file = os.path.join(dest, file)
	f=open(tji_file,'w')
	for t in tasks:
		if t.is_root_node():
			TaskDump(f,t,0)
	f.close()
	return tji_file

def JournalDumpToFile(proj, journals, dest='/tmp', file='auto_journal.tji'):
	# dump to journal file
	f = open(os.path.join(dest,file),'w')
	for b, j_list in journals.iteritems():
		f.write('supplement task %s {\n' % b.task.full_path)
		f.write('\tactual: booking %s %s-%s + %s' % (b.resource,b.when,b.part_of_day,b.length))
		if b.overtime: f.write('{ overtime %s }' % b.overtime)
		f.write('\n')
			
		# write journals
		for j in j_list:
			f.write('\tjournalentry %s "%s" {\n' % (b.when, smart_str(j.summary)))
			f.write('\t\talert %s\n' % j.alert)
			f.write('\t\tsummary "%s"\n' % smart_str(j.summary))
			f.write('\t\tdetails -8<-\n')
			f.write('\t\t\tDescription:\n\t\t\t%s\n' % smart_str(j.description))
			f.write('\t\t\tMemo:\n\t\t\t%s\n' % j.memo)
			f.write('\t\t->8-\n\t}\n')
		f.write('}\n')
	f.close()


def ReadTimesheet ():
	MAILSERVER='mail.crunchtime.local'
	mServer=poplib.POP3(MAILSERVER)
	mServer.user('fxia')
	mServer.pass_('XFxf12345')
	numMsg = len(mServer.list()[1])
	
	# get the lastest 100 emails
	messages = [mServer.retr(i) for i in range(numMsg,numMsg-100,-1)]
	messages = ['\n'.join(msg[1]) for msg in messages]
	messages= [parser.Parser().parsestr(msg) for msg in messages]
	for msg in messages:
		print msg['subject']
	mServer.quit()
                                
def TaskDumpToFreemind(proj, tasks):
	# write the root nodes
	root=etree.Element('map')
	root.set('version','0.9.0')
	project = etree.SubElement(root,'node')
	project.set('TEXT',proj.name)
	project.set('ID', str(proj.id))
	
	# dump tasks as nodes
	for t in tasks:
		if t.is_root_node():
			TaskIterateFreemind(project,t)
			
	# write the XML to Freemind file
	return etree.tostring(root, pretty_print=True)
		
def TaskIterateFreemind(root, task):
	# walk the task tree
	# this is essentially flattening the task tree to a XML
	
	# create the task node
	child = etree.SubElement(root,'node')
	child.set('CREATED',str(task.created))
	child.set('MODIFIED',str(task.last_modified))
	child.set('ID',str(task.id))
	child.set('TEXT',task.name)
	wbs_attr=etree.SubElement(child,'attribute')
	wbs_attr.set('NAME','REGISTERED')
	wbs_attr.set('VALUE',str(task.id))
	
	# create dependency as arrowlinks
	deps = TaskDependency.objects.filter(to_task=task)
	for d in deps:
		dep = etree.SubElement(child, 'arrowlink')
		dep.set('DESTINATION', str(d.from_task.id))

	# walk for its immediate children
	for d in task.get_children():
		TaskIterateFreemind(child,d)
	
def TaskDump(f, task, level):
	periods=TaskPeriod.objects.filter(task=task)
	resources=AllocateResource.objects.filter(task=task)
	deps = TaskDependency.objects.filter(from_task=task)
	overhead=level*'\t'
	
	f.write(overhead+'task %s "%s" {\n' % (CleanName(task.name), task.description))
	overhead+='\t'

	# all task tags	
	flags = [t.name for t in task.tags.all()]
	if len(flags):
		f.write(overhead+'flags '+','.join(flags)+'\n')
	
	# task periods
	for p in periods:
		if p.milestone: f.write(overhead+'milestone\n')
		if p.start: f.write(overhead+'%s: start %s\n' % (p.scenario, p.start))
		if p.max_start: f.write(overhead+'%s: maxstart %s\n' % (p.scenario, p.max_start))
		if p.min_start: f.write(overhead+'%s: minstart %s\n' % (p.scenario, p.min_start))
		if p.end: f.write(overhead+'%s: end %s\n' % (p.scenario, p.end))
		if p.max_end: f.write(overhead+'%s: maxend %s\n' % (p.scenario, p.max_end))
		if p.min_end: f.write(overhead+'%s: minend %s\n' % (p.scenario, p.min_end))
		if p.length: f.write(overhead+'%s: length %s\n' % (p.scenario, p.length))
		if p.duration: f.write(overhead+'%s: duration %s\n' % (p.scenario, p.duration))
		if p.effort: f.write(overhead+'%s: effort %s\n' % (p.scenario, p.effort))
		if p.complete != None:
			if int(p.complete)>=100:
				f.write(overhead+'%s: scheduled\n' % p.scenario)
			f.write(overhead+'%s: complete %s\n' %(p.scenario, p.complete))
	
	# write all resources
	tt=[]
	for r in resources:
		t = 'allocate %s' % r.primary
		if r.alternative: t += '{alternative %s}' % r.alternative
		tt.append(overhead+t)
	f.write('\n'.join(tt)+'\n')
	
	# write task dependencies
	for d in deps:
		f.write(overhead+'%s %s' %(d.type, d.to_task.full_path))
		if d.on_what:
			f.write('{ %s ' % d.on_what)
			if d.gap_duration:
				f.write('gapduration %s ' % d.gap_duration)
			if d.gap_length:
				f.write('gaplength %s ' % d.gap_length)
			f.write('}\n')
	
	if not task.is_leaf_node():
		for d in task.get_children():
			TaskDump(f,d,level+1)

	overhead=level*'\t'
	f.write(overhead+'}\n')

def CleanName(s):
	# clean up task name, replace ':' and white spaces with underscore '_'
	# first, replace all these with white spaces
	s=s.replace('!','')
	s=re.sub('\W',' ',s)
	s=re.sub('\s+',' ',s)
	
	#s = s.replace('_',' ').replace(':',' ').replace('.',' ')
	# second, capitalize first letter of each word
	s = capwords(s)
	# third, replace all white space with underscore
	return s.replace(' ','_')  

def ImportMPP(proj, f):
	xml = ''.join(f.chunks())
	root=etree.fromstring(xml)
	
	# read project info
	ns = {'ms' : 'http://schemas.microsoft.com/project'}

	# read and create all Task
	uid_dict=create_task_from_mpp (ns,proj,root)
	create_dependency_from_mpp(ns,proj,root,uid_dict)
	
def create_task_from_mpp(ns,p,root):
	all_tasks = []
	# iterate the "Task" nodes
	for t in root.xpath('//ms:Task',namespaces=ns):
		# uid
		uid=t.xpath('child::ms:UID',namespaces=ns)[0].text

		try:
			# name
			name=capwords(t.xpath('child::ms:Name',namespaces=ns)[0].text)
		except:
			continue
		
		# level
		level=int(t.xpath('child::ms:OutlineLevel',namespaces=ns)[0].text)	
		
		all_tasks.append((level,name,uid))
		
	temp = {}
	uid_dict={}
	for (level,name,uid) in all_tasks:
		# set up level and parent
		if level-1 in temp: parent=temp[level-1]
		else: temp[level]=parent=None
	
		task, created=Task.objects.get_or_create(project=p,name=name,level=level,parent=parent)
		
		# set up uid_dict
		uid_dict[uid]=task
		
		# this can be a parent for others
		temp[level]=task
	return uid_dict
	
def create_dependency_from_mpp(ns,p,root,uid_dict):
	# mpp predecessor type:
	# 1-FS,2-FF,3-SS,4-SF,0-None
	mpp_type={1:('depends','onend'),3:('depends','onstart'),
			2:('precedes','onend'),4:('precedes','onstart')}

	all_to_uid = []
	all_from_uid=[]
	
	for p in root.xpath('//ms:PredecessorLink',namespaces=ns):
		# predecessor UID, this is the to_task
		to_uid = p.xpath('child::ms:PredecessorUID',namespaces=ns)[0].text
		all_to_uid.append(to_uid)
			
		# mpp predecessor type
		p_type=mpp_type[int(p.xpath('child::ms:Type',namespaces=ns)[0].text)]
		
		# parent node is the SELF, this is the from_task
		from_uid = p.xpath('preceding-sibling::ms:UID',namespaces=ns)[0].text
		all_from_uid.append(from_uid)
		
		# look them up
		dep,created = TaskDependency.objects.get_or_create(from_task=uid_dict[from_uid], to_task=uid_dict[to_uid])
		
		# update dependencies, wow!
		dep.type, dep.on_what=p_type
		dep.save()
		
def ImportFreemind(f):
	xml=''.join(f.chunks())
	root = etree.fromstring(xml)
	tree = etree.ElementTree(root)
	
	# one node below root is the project
	proj = root.xpath('/map/node')[0]
	name = capwords(proj.get('TEXT'))
	# at lease one scenario should have been setup by now!
	s = ProjectScenario.objects.all()[0]
	projs = Project.objects.filter(name=name)
	if len(projs) == 0:
		# create project node		
		p = Project()
	else:
		p = projs[0]

	# update project with new information		
	p.name = name
	p.start=date.today()
	p.end=date.today()
	p.scenario = s
	p.save()
	
	# read and create all the TASKS node, including hiearchy
	create_task_from_freemind(tree,p,root.xpath('//node[@TEXT="Tasks"]')[0])
	create_dependency_from_freemind(tree)
	
def create_dependency_from_freemind(tree):
	# build task dependencies by following arrowlink
	for a in tree.xpath('//arrowlink[@DESTINATION]'):
		from_task = tree.xpath('//node[@ID="%s"]' % a.get('DESTINATION'))[0]
		wbs = from_task.xpath('child::attribute[@NAME="REGISTERED"]')
		if len(wbs):
			id = int(wbs[0].get('VALUE'))
			from_task = Task.objects.get(id=id)
		else: # if no wbs, use full_path
			from_task = Task.objects.get(full_path=tree.getpath(from_task))		
		if from_task is None: 1/0
		
		to_task = a.getparent()
		wbs = to_task.xpath('child::attribute[@NAME="REGISTERED"]')
		if len(wbs):
			id = int(wbs[0].get('VALUE'))
			to_task = Task.objects.get(id=id)
		else: # if no wbs, use full_path
			to_task = Task.objects.get(full_path=tree.getpath(to_task))
		if to_task is None: 1/0		
		
		deps = TaskDependency.objects.filter(from_task=from_task).filter(to_task=to_task)
		if len(deps) == 0:
			dep = TaskDependency()
		else: dep=deps[0]
		dep.from_task=from_task
		dep.to_task=to_task
		dep.type='depends' # hardcoded default, see model.py
		dep.on_what='onend'
		dep.save()
		
def create_task_from_freemind(tree, p, node, parent=None):
	# get task name
	name = capwords(node.get('TEXT'))
			
	# get task wbs if any
	wbs = node.xpath('child::attribute[@NAME="REGISTERED"]')
	if len(wbs):
		id = int(wbs[0].get('VALUE'))
		t = Task.objects.get(id=id)
	else: # if no wbs, use full_path
		full_path=tree.getpath(node)
		tasks = Task.objects.filter(project=p).filter(full_path=full_path).filter(name=name)
	
		if len(tasks) == 0:
			t = Task()
		else:
			t = tasks[0]
		t.full_path = full_path
	# this makes the task to be updated with new information
	t.name = name
	t.project=p
	if parent: t.parent=parent
	t.save()

	# get all the child
	for c in node.xpath('child::node'):
		create_or_update_task(tree,p,c,t)
		

